msg_tool\scripts\qlie\image/
dpng.rs

1//! Qlie tiled PNG image (.png)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::img::*;
6use crate::utils::psd::*;
7use crate::utils::struct_pack::*;
8use anyhow::Result;
9use msg_tool_macro::*;
10use std::io::{Read, Seek, Write};
11
12#[derive(StructPack, StructUnpack, Debug, Clone)]
13struct DpngHeader {
14    /// DPNG
15    magic: [u8; 4],
16    /// Seems to be always 1
17    _unk1: u32,
18    tile_count: u32,
19    image_width: u32,
20    image_height: u32,
21}
22
23#[derive(StructPack, StructUnpack, Debug, Clone)]
24struct Tile {
25    x: u32,
26    y: u32,
27    width: u32,
28    height: u32,
29    size: u32,
30    _unk: u64,
31    #[pack_vec_len(self.size)]
32    #[unpack_vec_len(size)]
33    png_data: Vec<u8>,
34}
35
36#[derive(StructPack, StructUnpack, Debug, Clone)]
37struct DpngFile {
38    header: DpngHeader,
39    #[pack_vec_len(self.header.tile_count)]
40    #[unpack_vec_len(header.tile_count)]
41    tiles: Vec<Tile>,
42}
43
44#[derive(Debug)]
45/// Qlie DPNG image builder
46pub struct DpngImageBuilder {}
47
48impl DpngImageBuilder {
49    pub fn new() -> Self {
50        Self {}
51    }
52}
53
54impl ScriptBuilder for DpngImageBuilder {
55    fn default_encoding(&self) -> Encoding {
56        Encoding::Utf8
57    }
58
59    fn build_script(
60        &self,
61        buf: Vec<u8>,
62        _filename: &str,
63        _encoding: Encoding,
64        _archive_encoding: Encoding,
65        config: &ExtraConfig,
66        _archive: Option<&Box<dyn Script>>,
67    ) -> Result<Box<dyn Script>> {
68        Ok(Box::new(DpngImage::new(MemReader::new(buf), config)?))
69    }
70
71    fn extensions(&self) -> &'static [&'static str] {
72        &["png"]
73    }
74
75    fn script_type(&self) -> &'static ScriptType {
76        &ScriptType::QlieDpng
77    }
78
79    fn is_image(&self) -> bool {
80        true
81    }
82
83    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
84        if buf_len >= 4 && buf.starts_with(b"DPNG") {
85            Some(20)
86        } else {
87            None
88        }
89    }
90
91    fn can_create_image_file(&self) -> bool {
92        true
93    }
94
95    fn create_image_file<'a>(
96        &'a self,
97        data: ImageData,
98        filename: &str,
99        writer: Box<dyn WriteSeek + 'a>,
100        options: &ExtraConfig,
101    ) -> Result<()> {
102        if options.qlie_dpng_use_raw_png {
103            create_raw_png_image(filename, writer, None)
104        } else {
105            create_image(data, writer, options)
106        }
107    }
108}
109
110#[derive(Debug)]
111pub struct DpngImage {
112    img: DpngFile,
113    config: ExtraConfig,
114}
115
116impl DpngImage {
117    pub fn new<T: Read + Seek>(mut data: T, config: &ExtraConfig) -> Result<Self> {
118        let img = DpngFile::unpack(&mut data, false, Encoding::Utf8, &None)?;
119        if img.header.magic != *b"DPNG" {
120            anyhow::bail!("Not a valid DPNG image");
121        }
122        if img.tiles.is_empty() {
123            anyhow::bail!("DPNG image has no tiles");
124        }
125        Ok(DpngImage {
126            img,
127            config: config.clone(),
128        })
129    }
130}
131
132impl Script for DpngImage {
133    fn default_output_script_type(&self) -> OutputScriptType {
134        OutputScriptType::Custom
135    }
136
137    fn is_output_supported(&self, output: OutputScriptType) -> bool {
138        matches!(output, OutputScriptType::Custom)
139    }
140
141    fn default_format_type(&self) -> FormatOptions {
142        FormatOptions::None
143    }
144
145    fn custom_output_extension<'a>(&'a self) -> &'a str {
146        "psd"
147    }
148
149    fn is_image(&self) -> bool {
150        if self.config.qlie_dpng_psd {
151            false
152        } else {
153            true
154        }
155    }
156
157    fn export_image(&self) -> Result<ImageData> {
158        let (idx, tile) = self
159            .img
160            .tiles
161            .iter()
162            .enumerate()
163            .find(|(_, t)| t.size != 0)
164            .ok_or_else(|| anyhow::anyhow!("DPNG image has no valid tiles with PNG data"))?;
165        let mut base = load_png(MemReaderRef::new(&tile.png_data))?;
166        convert_to_rgba(&mut base)?;
167        let mut base = draw_on_canvas(
168            base,
169            self.img.header.image_width,
170            self.img.header.image_height,
171            tile.x,
172            tile.y,
173        )?;
174        for tile in &self.img.tiles[idx + 1..] {
175            if tile.size == 0 {
176                continue;
177            }
178            let mut diff = load_png(MemReaderRef::new(&tile.png_data))?;
179            convert_to_rgba(&mut diff)?;
180            draw_on_image(&mut base, &diff, tile.x, tile.y)?;
181        }
182        Ok(base)
183    }
184
185    fn import_image<'a>(
186        &'a self,
187        data: ImageData,
188        filename: &str,
189        file: Box<dyn WriteSeek + 'a>,
190    ) -> Result<()> {
191        if self.config.qlie_dpng_use_raw_png {
192            let img = load_png(std::fs::File::open(filename)?)?;
193            if img.width != self.img.header.image_width
194                || img.height != self.img.header.image_height
195            {
196                eprintln!(
197                    "Warning: Image dimensions do not match original DPNG image (expected {}x{}, got {}x{})",
198                    self.img.header.image_width,
199                    self.img.header.image_height,
200                    img.width,
201                    img.height
202                );
203                crate::COUNTER.inc_warning();
204            }
205            create_raw_png_image(filename, file, Some(img))?;
206        } else {
207            if data.width != self.img.header.image_width
208                || data.height != self.img.header.image_height
209            {
210                eprintln!(
211                    "Warning: Image dimensions do not match original DPNG image (expected {}x{}, got {}x{})",
212                    self.img.header.image_width,
213                    self.img.header.image_height,
214                    data.width,
215                    data.height
216                );
217                crate::COUNTER.inc_warning();
218            }
219            create_image(data, file, &self.config)?;
220        }
221        Ok(())
222    }
223
224    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
225        let mut psd = PsdWriter::new(
226            self.img.header.image_width,
227            self.img.header.image_height,
228            ImageColorType::Rgba,
229            8,
230            encoding,
231        )?
232        .compress(self.config.psd_compress)
233        .zlib_compression_level(self.config.zlib_compression_level);
234        let (idx, tile) = self
235            .img
236            .tiles
237            .iter()
238            .enumerate()
239            .find(|(_, t)| t.size != 0)
240            .ok_or_else(|| anyhow::anyhow!("DPNG image has no valid tiles with PNG data"))?;
241        let mut base = load_png(MemReaderRef::new(&tile.png_data))?;
242        convert_to_rgba(&mut base)?;
243        psd.add_layer(
244            &format!("layer_{}", idx),
245            tile.x,
246            tile.y,
247            base.clone(),
248            None,
249        )?;
250        let mut base = draw_on_canvas(
251            base,
252            self.img.header.image_width,
253            self.img.header.image_height,
254            tile.x,
255            tile.y,
256        )?;
257        let mut idx2 = idx;
258        for tile in &self.img.tiles[idx + 1..] {
259            idx2 += 1;
260            if tile.size == 0 {
261                continue;
262            }
263            let mut diff = load_png(MemReaderRef::new(&tile.png_data))?;
264            convert_to_rgba(&mut diff)?;
265            draw_on_image(&mut base, &diff, tile.x, tile.y)?;
266            psd.add_layer(&format!("layer_{}", idx2), tile.x, tile.y, diff, None)?;
267        }
268        let file = std::fs::File::create(filename)?;
269        let mut writer = std::io::BufWriter::new(file);
270        psd.save(base, &mut writer)?;
271        Ok(())
272    }
273
274    fn custom_import<'a>(
275        &'a self,
276        custom_filename: &'a str,
277        mut file: Box<dyn WriteSeek + 'a>,
278        encoding: Encoding,
279        output_encoding: Encoding,
280    ) -> Result<()> {
281        let rfile = std::fs::File::open(custom_filename)?;
282        let mut reader = std::io::BufReader::new(rfile);
283        let psd = PsdReader::new(&mut reader, output_encoding)?;
284        let width = psd.width();
285        let height = psd.height();
286        let layers = psd.read_normal_layers()?;
287        let header = DpngHeader {
288            magic: *b"DPNG",
289            _unk1: 1,
290            tile_count: layers.len() as u32,
291            image_width: width,
292            image_height: height,
293        };
294        let mut tiles = Vec::new();
295        for layer in layers {
296            let data = layer.image()?;
297            let width = data.width;
298            let height = data.height;
299            let mut png_data = MemWriter::new();
300            encode_img_writer(data, ImageOutputType::Png, &mut png_data, &self.config)?;
301            let png_data = png_data.into_inner();
302            let tile = Tile {
303                x: layer.left() as u32,
304                y: layer.top() as u32,
305                width,
306                height,
307                size: png_data.len() as u32,
308                _unk: 0,
309                png_data,
310            };
311            tiles.push(tile);
312        }
313        let dpng = DpngFile { header, tiles };
314        dpng.pack(&mut file, false, encoding, &None)?;
315        Ok(())
316    }
317}
318
319fn create_raw_png_image<'a>(
320    filename: &str,
321    mut file: Box<dyn WriteSeek + 'a>,
322    img: Option<ImageData>,
323) -> Result<()> {
324    let img = match img {
325        Some(img) => img,
326        None => load_png(std::fs::File::open(filename)?)?,
327    };
328    let header = DpngHeader {
329        magic: *b"DPNG",
330        _unk1: 1,
331        tile_count: 1,
332        image_width: img.width,
333        image_height: img.height,
334    };
335    let png_data = crate::utils::files::read_file(filename)?;
336    let tile = Tile {
337        x: 0,
338        y: 0,
339        width: img.width,
340        height: img.height,
341        size: png_data.len() as u32,
342        _unk: 0,
343        png_data,
344    };
345    let dpng = DpngFile {
346        header,
347        tiles: vec![tile],
348    };
349    dpng.pack(&mut file, false, Encoding::Utf8, &None)?;
350    Ok(())
351}
352
353fn create_image<'a>(
354    image: ImageData,
355    mut writer: Box<dyn WriteSeek + 'a>,
356    config: &ExtraConfig,
357) -> Result<()> {
358    let header = DpngHeader {
359        magic: *b"DPNG",
360        _unk1: 1,
361        tile_count: 1,
362        image_width: image.width,
363        image_height: image.height,
364    };
365    let mut png_data = MemWriter::new();
366    let width = image.width;
367    let height = image.height;
368    encode_img_writer(image, ImageOutputType::Png, &mut png_data, config)?;
369    let png_data = png_data.into_inner();
370    let tile = Tile {
371        x: 0,
372        y: 0,
373        width,
374        height,
375        size: png_data.len() as u32,
376        _unk: 0,
377        png_data,
378    };
379    let dpng = DpngFile {
380        header,
381        tiles: vec![tile],
382    };
383    dpng.pack(&mut writer, false, Encoding::Utf8, &None)?;
384    Ok(())
385}